#pragma once
#include <string>
#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include <unistd.h>
#include "Log.hpp"
#include "Socket.hpp"
using namespace socket_ns;
class EpollServer
{
    const static int size = 128;
    const static int num = 128;

public:
    EpollServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(port);
        _epfd = ::epoll_create(size);
        if (_epfd < 0)
        {
            LOG(FATAL, "epoll create error\n");
            exit(1);
        }

        LOG(INFO, "epoll create success, epfd: %d\n", _epfd); // 4
    }
    void InitServer()
    {
        // 在获取连接前，需要先把listen套接字添加到epoll模型里(使用epoll_ctl)
        // 构建好epoll_event结构体
        struct epoll_event ev;
        // 新连接到来，需要关注的是读事件就绪
        ev.events = EPOLLIN;
        // ev.events = EPOLLIN | EPOLLET;

        ev.data.fd = _listensock->Sockfd();
        // 关心listen套接字的ev事件
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->Sockfd(), &ev);
        if (n < 0)
        {
            LOG(FATAL, "epoll_ctl error!\n");
            exit(2);
        }
        LOG(INFO, "epoll_ctl success! add new sockfd: %d\n", _listensock->Sockfd());
    }
    std::string EventsToString(u_int32_t events)
    {
        std::string eventsstr;
        if (events & EPOLLIN)
            eventsstr = "EPOLLIN ";
        if (events & EPOLLOUT)
            eventsstr += "| EPOLLOUT";

        return eventsstr;
    }

    void Accepter()
    {
        InetAddr addr;
        int sockfd = _listensock->Accepter(&addr);
        if (sockfd < 0)
        {
            LOG(ERROR, "获取连接失败\n");
            return;
        }
        LOG(INFO, "得到了一个新的连接: %d, 客户端信息：%s:%d\n", sockfd, addr.Ip().c_str(), addr.Port());

        // 得到了一个新的sockfd，我们需不需要等待写的就绪，不需要，一个新的sockfd，先天就具备写事件就绪
        // 等底层有数据（读时间就绪），read/recv才不会被阻塞
        // epoll清楚底层有数据
        // 因此先将新的sockfd添加到epoll中，通过之后的监管，就能知道什么事件就绪
        struct epoll_event ev;
        ev.data.fd = sockfd;
        ev.events = EPOLLIN;
        ::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
        LOG(INFO, "epoll_ctl success! add new sockfd: %d\n", sockfd);
    }

    void HandlerIO(int fd)
    {
        char buffer[4096];
        // 不会阻塞，因为读事件就绪
        int n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer;
            // 构建一个简单的应答
            std::string response = "HTTP/1.0 200 OK\r\n";
            std::string content = "<html><body><h1>hello pupu, hello epoll</h1></body></html>";
            response += "Content-Type: text/html\r\n";
            response += "Content-Length: " + std::to_string(content.size()) + "\r\n";
            response += "\r\n";
            response += content;
            // 发送缓冲区有空间，写事件就就绪
            ::send(fd, response.c_str(), response.size(), 0);
        }
        else if (n == 0) // 说明对方关闭了连接，因此需要close，并且删除
        {
            LOG(INFO, "client quit, close fd: %d\n", fd);
            // 关闭连接时产生的套接字描述符，从epoll模型中移除该文件描述符
            //  1.先移除
            epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr); // 红黑树底层是用的fd做的键值，因此并不需要写清楚事件是什么
            // 2.关闭文件描述符
            ::close(fd);
        }
        else
        {
            LOG(ERROR, "recv error, close fd: %d\n", fd);
            // 1.先移除
            epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr); // 红黑树底层是用的fd做的键值，因此并不需要写清楚事件是什么
            // 2.关闭文件描述符
            ::close(fd);
        }
    }

    void HandlerEvent(int n)
    {
        for (int i = 0; i < n; i++)
        {
            int fd = revs[i].data.fd;
            uint32_t revents = revs[i].events;

            // 因为revents只是一个简单的标志位，因此我们设计一个接口能直接看到是什么事件
            LOG(INFO, "%d 上有事件就绪了，具体事件是：%s\n", fd, EventsToString(revents).c_str());
            if (fd == _listensock->Sockfd())
                Accepter();
            else // 处理其他套接字
                HandlerIO(fd);
        }
    }

    void Loop()
    {
        int timeout = -1;
        while (true)
        {
            // 通过epoll等待事件就绪后才能获取连接
            int n = ::epoll_wait(_epfd, revs, num, timeout); // 每隔1s
            switch (n)
            {
            case 0:
                LOG(INFO, "epoll time out...\n");
                break;
            case -1:
                LOG(ERROR, "epoll error...\n"); // 不会出现
                break;
            default:
                LOG(INFO, "haved event happened!, n: %d\n", n);
                HandlerEvent(n);
                break;
            }
        }
    }
    ~EpollServer()
    {
        if (_epfd >= 0)
            ::close(_epfd);

        _listensock->Close();
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    int _epfd;
    struct epoll_event revs[num];
};
